﻿

using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using System.Threading.Tasks;
using LightSO.ILibrary;

namespace LightSO.ILibrary
{
    /// <summary>
    /// 表达式组合
    /// </summary>
    public static class ExpExtensions
    {
	    /// <summary>
        /// 排序方法
        /// </summary>
        /// <param name="o">字段</param>
        /// <returns>true</returns>
        public static bool Desc(this object o)
        {
            return true;
        }

        /// <summary>
        /// 排序方法
        /// </summary>
        /// <param name="o">字段</param>
        /// <returns>false</returns>
        public static bool Asc(this object o)
        {
            return false;
        }

        /// <summary>
        /// 逻辑与表达式运算
        /// </summary>
        /// <typeparam name="T">数据库实体类型</typeparam>
        /// <param name="leftExpression">左表达式</param>
        /// <param name="rightExpression">右表达式</param>
        /// <returns></returns>
        public static Expression<T> And<T>(this Expression<T> leftExpression, Expression<T> rightExpression)
        {
            if (leftExpression == null)
                return rightExpression;

            if (rightExpression == null)
                return leftExpression;

            if (leftExpression.Type.GetMethod("Invoke").ReturnType != typeof(Boolean))
                throw new ArgumentException("lambda expressions must return boolean type");

            var args = leftExpression.Parameters.ToArray();
            var right = new ExpressionVisitor<T>().Replace(rightExpression.Body, rightExpression.Parameters.ToArray(), args);
            var exp = Expression.AndAlso(leftExpression.Body, right);

            return Expression.Lambda<T>(exp, args);
        }
        
        /// <summary>
        /// 逻辑或表达式运算
        /// </summary>
        /// <typeparam name="T">数据库实体类型</typeparam>
        /// <param name="leftExpression">左表达式</param>
        /// <param name="rightExpression">右表达式</param>
        /// <returns></returns>
        public static Expression<T> Or<T>(this Expression<T> leftExpression, Expression<T> rightExpression)
        {
            if (leftExpression == null)
                return rightExpression;

            if (rightExpression == null)
                return leftExpression;

            if (leftExpression.Type.GetMethod("Invoke").ReturnType != typeof(Boolean))
                throw new ArgumentException("lambda expressions must return boolean type");

            var args = leftExpression.Parameters.ToArray();
            var right = new ExpressionVisitor<T>().Replace(rightExpression.Body, rightExpression.Parameters.ToArray(), args);
            var exp = Expression.OrAssign(leftExpression.Body, right);

            return Expression.Lambda<T>(exp, args);
        }

        public static IOrderedQueryable<T> OrderWith<T>(this IQueryable<T> q, Expression<Func<T, bool>> orderby)
        {
            LambdaExpression order = orderby;

            if (orderby.Body.NodeType == ExpressionType.Conditional)
            {
                if ((bool)Expression.Lambda(((ConditionalExpression)orderby.Body).Test).Compile().DynamicInvoke())
                    order = Expression.Lambda(((ConditionalExpression)orderby.Body).IfTrue, orderby.Parameters);
                else
                    order = Expression.Lambda(((ConditionalExpression)orderby.Body).IfFalse, orderby.Parameters);
            }
            if (order.Body.NodeType != ExpressionType.Call)
                throw new NotSupportedException(string.Format("{0} is not supported!", order.NodeType.ToString()));

            var exp = ((MethodCallExpression)order.Body).Arguments[0];
            var sort = Expression.Lambda<Func<T, object>>(exp, order.Parameters);
            if (((MethodCallExpression)order.Body).Method.Name == "Desc")
                return q.OrderByDescending(sort);

            return q.OrderBy(sort);
        }

        public static IOrderedQueryable<T> ThenWith<T>(this IOrderedQueryable<T> q, Expression<Func<T, bool>> orderby)
        {
            LambdaExpression order = orderby;

            if (orderby.Body.NodeType == ExpressionType.Conditional)
            {
                if ((bool)Expression.Lambda(((ConditionalExpression)orderby.Body).Test).Compile().DynamicInvoke())
                    order = Expression.Lambda(((ConditionalExpression)orderby.Body).IfTrue, orderby.Parameters);
                else
                    order = Expression.Lambda(((ConditionalExpression)orderby.Body).IfFalse, orderby.Parameters);
            }
            if (order.Body.NodeType != ExpressionType.Call)
                throw new NotSupportedException(string.Format("{0} is not supported!", order.NodeType.ToString()));

            var exp = ((MethodCallExpression)order.Body).Arguments[0];
            var sort = Expression.Lambda<Func<T, object>>(exp, order.Parameters);
            if (((MethodCallExpression)order.Body).Method.Name == "Desc")
                return q.ThenByDescending(sort);

            return q.ThenBy(sort);
        }

        internal class ExpressionVisitor<T> : System.Linq.Expressions.ExpressionVisitor
        {
            /// <summary>
            /// 第一表达式的节点
            /// </summary>
            Expression[] _nodes1;
            /// <summary>
            /// 第二表达式的节点
            /// </summary>
            Expression[] _nodes2;
            
            /// <summary>
            /// 
            /// </summary>
            /// <param name="node"></param>
            /// <returns></returns>
            public override Expression Visit(Expression node)
            {
                if (_nodes1 == null)
                    return base.Visit(node);

                var index = Array.IndexOf(_nodes1, node);
                return index >= 0 ? _nodes2[index] : base.Visit(node);
            }

            public Expression Replace(Expression expr, Expression[] nodes1, Expression[] nodes2)
            {
                _nodes1 = nodes1;
                _nodes2 = nodes2;
                return Visit(expr);
            }
        }

    }
}


